package com.tools.genxsd.util;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.thoughtworks.xstream.io.xml.XppDriver;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.dom4j.*;
import org.dom4j.io.SAXReader;
import org.dom4j.io.SAXValidator;
import org.dom4j.util.XMLErrorHandler;
import org.xml.sax.SAXException;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import java.io.ByteArrayInputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.List;

/**
 * @author xin.zh
 * @Description xml处理工具类: xsd校验、xml报文与对象之间的转换等
 */
public class XmlUtil {

    /**
     * @param xsdPath xsd路径
     * @param xmlPath 待验证xml路径
     * @return
     */
    public static Result validatexmlbyxsd(String xsdPath, String xmlPath) {
        URL xmlFileName = null;
        String xsdFileName = null;
        try {
            xmlFileName = XmlUtil.class.getClassLoader().getResource(xmlPath);
            xsdFileName = XmlUtil.class.getClassLoader().getResource(xsdPath).toString();
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setValidating(true);
            factory.setNamespaceAware(true);

            SAXParser parser = factory.newSAXParser();
            parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                    "http://www.w3.org/2001/XMLSchema");
            parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", xsdFileName);

            SAXReader xmlReader = new SAXReader();
            Document xmlDocument = (Document) xmlReader.read(xmlFileName);
            return validate(null, parser, xmlDocument);
        } catch (Exception ex) {
            ex.printStackTrace();
            String format = String.format("XML文件: %s 通过XSD文件:%s 检验失败。%s ,原因：%s "
                    , xmlFileName, xsdFileName,
                    System.getProperty("line.separator"), ex.getMessage());
            System.out.println(format);
            return Result.buildFail(ex.getMessage());
        }
    }

    /**
     * @param xsdPath   xsd路径
     * @param xmlString 待验证xml字符串
     * @param traceNo
     * @return
     */
    public static Result validateXmlFormatByxsd(String xsdPath, String xmlString, String traceNo) {
        String xsdFileName = null;
        try {
            xsdFileName = XmlUtil.class.getClassLoader().getResource(xsdPath).toString();
            SAXParserFactory factory = SAXParserFactory.newInstance();
            factory.setValidating(true);
            factory.setNamespaceAware(true);

            SAXParser parser = factory.newSAXParser();
            parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
                    "http://www.w3.org/2001/XMLSchema");
            parser.setProperty("http://java.sun.com/xml/jaxp/properties/schemaSource", xsdFileName);

            SAXReader xmlReader = new SAXReader();
            Document xmlDocument = xmlReader.read(new ByteArrayInputStream(xmlString.getBytes(StandardCharsets.UTF_8)));
            return validate(traceNo, parser, xmlDocument);
        } catch (Exception ex) {
            String format = String.format("报文校验traceNo:%s XML报文:%s 通过XSD文件:%s 检验失败。原因：%s", traceNo,
                    xmlString, xsdFileName, ex.getMessage());
            System.out.println(format);
            return Result.buildFail(ex.getMessage());
        }
    }

    public static Result validate(String traceNo, SAXParser parser, Document xmlDocument) throws SAXException {
        XMLErrorHandler errorHandler = new XMLErrorHandler();
        SAXValidator validator = new SAXValidator(parser.getXMLReader());
        validator.setErrorHandler(errorHandler);
        validator.validate(xmlDocument);
        if (errorHandler.getErrors().hasContent()) {
            Element e = errorHandler.getErrors();
            List<Node> list = e.content();
            StringBuffer result = new StringBuffer("报文存在").append(list.size()).append("条错误, 错误明细如下:\n");
            for (Node node : list) {
                result.append(node.getText().split(":", 2)[1]).append("\n");
            }

            String format = String.format("报文校验traceNo:[%s] 错误信息:[%s]", traceNo, result.toString());
            System.out.println(format);
            return Result.buildFail(result.toString());
        }
        return Result.buildPass();
    }

    /**
     * 通过xstream组装报文
     *
     * @param obj
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> String toXml(String version, String encoding, T obj) {
        String xml = null;
        try {
            XStream xstream = new XStream(new XppDriver(new XmlFriendlyNameCoder("_-", "_")));
            xstream.processAnnotations(obj.getClass());
            xstream.autodetectAnnotations(true);
            xstream.aliasSystemAttribute(null, "class");
            xml = xstream.toXML(obj);
            xml = "<?xml version=\"" + version + "\" encoding=\"" + encoding + "\" ?>" + System.getProperty("line.separator", "\n") + xml;
        } catch (Exception e) {
            throw new RuntimeException("编码服务器数据错误", e);
        }
        return xml;
    }

    @NoArgsConstructor
    @AllArgsConstructor
    public static class Result {
        /**
         * 是否通过校验
         */
        @Getter
        private boolean approve;

        /**
         * 未通过时返回错误描述
         */
        @Getter
        private String errorMsg;

        public Result(boolean approve) {
            this.approve = approve;
        }

        public static Result buildFail(String errorMsg) {
            return new Result(false, errorMsg);
        }

        public static Result buildPass() {
            return new Result(true);
        }
    }
}
